package ws

import (
	"bytes"
	"context"
	"errors"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/duke-git/lancet/v2/cryptor"
	"github.com/duke-git/lancet/v2/random"
	"github.com/gorilla/websocket"
)

const (
	// 允许向对方写入消息的时间
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 9096

	// send buffer size
	bufSize = 1024
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

type Hub struct {
	conn       *websocket.Conn
	clients    sync.Map
	register   chan *Client
	unregister chan *Client
	ctx        context.Context
	onMessage  func(ctx context.Context, c *Client, b []byte)
	onConn     func(ctx context.Context, c *Client)
	onClose    func(ctx context.Context, c *Client)
}

type Huboption func(*Hub)

func SetOnMessage(fun func(context.Context, *Client, []byte)) Huboption {
	return func(h *Hub) {
		h.onMessage = fun
	}
}

func SetOnConn(fun func(context.Context, *Client)) Huboption {
	return func(h *Hub) {
		h.onConn = fun
	}
}

func SetOnClose(fun func(context.Context, *Client)) Huboption {
	return func(h *Hub) {
		h.onClose = fun
	}
}
func NewHub(ctx context.Context, opts ...Huboption) *Hub {
	h := &Hub{
		register:   make(chan *Client),
		unregister: make(chan *Client),
		ctx:        ctx,
		onMessage:  func(ctx context.Context, c *Client, b []byte) {},
		onConn:     func(ctx context.Context, c *Client) {},
		onClose:    func(ctx context.Context, c *Client) {},
	}

	for _, opt := range opts {
		opt(h)
	}
	return h
}

func (h *Hub) Run() {
	for {
		select {
		case client := <-h.register:
			//h.clients[client.Name] = client
			h.clients.Store(client.Name, client)
			h.onConn(h.ctx, client)
		case client := <-h.unregister:
			h.clients.Delete(client.Name)
			close(client.send)
			h.onClose(h.ctx, client)
		}
	}
}

func (h *Hub) Send(name string, message []byte) error {
	if client, ok := h.clients.Load(name); ok {
		if c, isok := client.(*Client); isok {
			c.send <- message
			return nil
		}
	}
	return errors.New("not found " + name)
}

func (h *Hub) All() []string {
	var ret []string
	h.clients.Range(func(key, value any) bool {
		ret = append(ret, key.(string))
		return true
	})
	return ret
}

func (h *Hub) CloseAll() {
	h.clients.Range(func(key, value any) bool {
		if v, ok := value.(*Client); ok {
			_ = v.conn.Close()
		}
		return true
	})
}

func (h *Hub) SendAll(message []byte) {
	h.clients.Range(func(key, value any) bool {
		if v, ok := value.(*Client); ok {
			v.send <- message
		}
		return true
	})
}

type Client struct {
	hub  *Hub
	conn *websocket.Conn
	send chan []byte
	Name string
}

func (c *Client) Hub() *Hub {
	return c.hub
}

func (c *Client) readPump() {
	defer func() {
		c.hub.unregister <- c
		c.conn.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("error: %v", err)
			}
			break
		}
		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
		c.hub.onMessage(c.hub.ctx, c, message)

	}
}

func (c *Client) writePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			w.Write(message)

			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				w.Write(<-c.send)
			}

			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func ServeWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	name := createName()
	client := &Client{
		hub:  hub,
		conn: conn,
		send: make(chan []byte, bufSize),
		Name: name,
	}
	client.hub.register <- client
	go client.writePump()
	go client.readPump()
}

func Send(c *Client, message []byte) {
	c.send <- message
}
func createName() string {
	return cryptor.Md5String(time.Now().Format("060102150304") + random.RandString(15))
}
